﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Flatland.GeneticProgramming.CoreTypes;
using Flatland.GeneticProgramming.SyntaxTree;
using Flatland.Utilities;

namespace Flatland.GeneticProgramming.Initializations {
    /// <summary>
    /// UseTreeBuild to constuct an expression Tree.
    /// </summary>
    public class FullInitialization : IInitialization {

        /// <summary>
        /// Interface realization.
        /// </summary>
        /// <param name="depth">The maximun depth of the Tree.</param>
        /// <param name="primitiveSet">The PrimitiveSet object to build the Tree from.</param>
        public Tree Init(uint depth, PrimitiveSet primitiveSet) {
            return Init(depth, primitiveSet, false);
        }

        /// <summary>
        /// Create a new Tree. Nodes and leaves are of type Function.
        /// A Node will have as many children as there are parameters to the funtion
        /// Depth 1 will return the root Node. Depth 0 will return null
        /// </summary>
        /// <param name="depth">Max depth of the Tree</param>
        /// <param name="primitiveSet">PrimitiveSet to build from</param>
        /// <param name="grow">Grow method if true</param>
        /// <returns></returns>
        public Tree Init(uint depth, PrimitiveSet primitiveSet, bool grow) {
            Debug.Assert(primitiveSet.TerminalSet.Count != 0, "Trees cannot be create with terminals");
            if (primitiveSet == null) throw new ArgumentNullException("primitiveSet");
            if (depth == 0) return null;

            //Grow option
            if (1 < depth && grow && Utils.Random.Next(2) == 0)
                depth--;

            if (depth == 1 || primitiveSet.FunctionSet.Count == 0) {
                Terminal term = null;
                if (primitiveSet.IsCommand) {
                    PrimitiveSet cmds = primitiveSet.CommandSets();
                    term = cmds.TerminalSet[Utils.Random.Next(cmds.TerminalSet.Count())];
                }
                else {
                    term = primitiveSet.TerminalSet[Utils.Random.Next(primitiveSet.TerminalSet.Count())];
                }
                return new Leaf(term);
            }

            Function rootFun = null;
            if (primitiveSet.IsCommand)
                rootFun = Utils.Random.Next(2) == 1
                              ? primitiveSet.FunctionSet.First(x => x.SetType == SetType.IF_THEN_ELSE)
                              : primitiveSet.FunctionSet.First(x => x.SetType == SetType.SEQUENCE);
            else
                rootFun = primitiveSet.FunctionSet[Utils.Random.Next(primitiveSet.FunctionSet.Count())];


            Node Node = new Node(rootFun);
            switch (rootFun.SetType) {
                case SetType.IF_THEN_ELSE:
                    Node.Children.Add(_Build(depth - 1, primitiveSet.BooleanSets(), Node, grow));
                    Node.Children.Add(_BuildCommand(depth - 1, primitiveSet, Node, grow));
                    Node.Children.Add(_BuildCommand(depth - 1, primitiveSet, Node, grow));
                    break;
                case SetType.SEQUENCE:
                    for (int i = 0; i < GeneticProgramming.GP.MAX_COMMAND_SEQUENCE; i++) {
                        Node.Children.Add(_BuildCommand(depth - 1, primitiveSet, Node, grow));
                    }
                    break;
                default:
                    foreach (var item in rootFun.MethodInfo.GetParameters()) {
                        Node.Children.Add(_Build(depth - 1, primitiveSet, Node, grow));
                    }
                    break;
            }
            return Node;
        }

        /// <summary>
        /// Overloaded build.
        /// </summary>
        /// <param name="depth">Max depth</param>
        /// <param name="functionSet">Function set</param>
        /// <param name="terminalSet">Terminal set</param>
        /// <returns></returns>
        public Tree Init(uint depth, List<Function> functionSet, List<Terminal> terminalSet) {
            if (functionSet == null) throw new ArgumentNullException("functionSet");
            if (terminalSet == null) throw new ArgumentNullException("terminalSet");
            if (depth == 0) return null;
            return Init(depth, new PrimitiveSet() {
                FunctionSet = functionSet,
                TerminalSet = terminalSet
            });
        }

        /// <summary>
        /// Private. Build node with any primitive.
        /// </summary>
        /// <param name="depth">Max depth</param>
        /// <param name="primitiveSet">PrimitiveSet</param>
        /// <param name="parent">Parent node</param>
        /// <param name="grow">True if grow method</param>
        /// <returns></returns>
        private static Tree _Build(uint depth, PrimitiveSet primitiveSet, Tree parent, bool grow) {
            //Grow option
            if (1 < depth && grow && Utils.Random.Next(2) == 0)
                depth--;
            if (depth == 1) {
                Leaf leaf = new Leaf(primitiveSet.TerminalSet[Utils.Random.Next(primitiveSet.TerminalSet.Count())]);
                leaf.Parent = parent;
                return leaf;
            }
            if (!primitiveSet.FunctionSet.Any()) return _Build(1, primitiveSet, parent, grow);
            Function fun = primitiveSet.FunctionSet[Utils.Random.Next(primitiveSet.FunctionSet.Count())];
            Node Node = new Node(fun);
            Node.Parent = parent;
            foreach (var item in fun.MethodInfo.GetParameters()) {
                Node.Children.Add(_Build(depth - 1, primitiveSet, Node, grow));
            }
            return Node;
        }

        /// <summary>
        /// Private. Build command node. 
        /// Leafs will be commands.
        /// Can handle if_then_else and sequence 
        /// </summary>
        /// <param name="depth">Max depth</param>
        /// <param name="primitiveSet">PrimitiveSet</param>
        /// <param name="parent">Parent node</param>
        /// <param name="grow">True if grow method</param>
        /// <returns></returns>
        private Tree _BuildCommand(uint depth, PrimitiveSet primitiveSet, Tree parent, bool grow) {
            //Grow option
            if (1 < depth && grow && Utils.Random.Next(2) == 0)
                depth--;
            if (depth == 1) {
                PrimitiveSet cmds = primitiveSet.CommandSets();
                Leaf leaf = new Leaf(cmds.TerminalSet[Utils.Random.Next(cmds.TerminalSet.Count())]);
                leaf.Parent = parent;
                return leaf;
            }
            PrimitiveSet cmd = primitiveSet.CommandSets();
            Function fun = cmd.FunctionSet[Utils.Random.Next(cmd.FunctionSet.Count())];
            Node Node = new Node(fun);
            Node.Parent = parent;
            switch (fun.SetType) {
                case SetType.IF_THEN_ELSE:
                    Node.Children.Add(_Build(depth - 1, primitiveSet.BooleanSets(), Node, grow));
                    Node.Children.Add(_BuildCommand(depth - 1, primitiveSet, Node, grow));
                    Node.Children.Add(_BuildCommand(depth - 1, primitiveSet, Node, grow));
                    break;
                case SetType.SEQUENCE:
                    for (int i = 0; i < GeneticProgramming.GP.MAX_COMMAND_SEQUENCE; i++) {
                        Node.Children.Add(_BuildCommand(depth - 1, primitiveSet, Node, grow));
                    }
                    break;
            }
            return Node;
        }
    }
}
